
#include <vector>
#include <apr-1/apr_date.h>
#include "../utils/StringUtils.h"
#include "../iface/APRInterface.h"
#include "NewActorsDB.h"

#define FATAL_IF_BLANK(terrcode, tstring, tdescr) \
   do \
   { \
      if (((tstring) == NULL) || (strlen(tstring) == 0)) \
      { \
         errprint("%s cannot be %s string", (tdescr), (tstring) ? "an empty" : "a NULL"); \
         return (terrcode); \
      } \
   } while (0)
   
#define FATAL_IF_BLANK_VECTOR_UNLOCK_MUTEX(terrcode, tvector, tdescr) \
   do \
   { \
      if (((tvector) == NULL) || ((tvector)->size() == 0)) \
      { \
         errprint("this vector corresponding to %s cannot be %s", (tdescr), (tvector) ? "empty" : "NULL"); \
         if (tvector) delete(tvector); \
         sqlite3_mutex_leave(dbmutex); \
         return (terrcode); \
      } \
   } while (0)
   
#define FATAL_IF_NULL(terrcode, tobj, tdescr) \
   do \
   { \
      if ((tobj) == NULL) \
      { \
         errprint("%s cannot be NULL", (tdescr)); \
         return (terrcode); \
      } \
   } while (0)
   
#define FATAL_IF_EXPRESSION(terrcode, texpr, tdescr, ...) \
   do \
   { \
      if (texpr) \
      { \
         errprint(tdescr, ## __VA_ARGS__); \
         return (terrcode); \
      } \
   } while (0)

const char *NewActorsDB::table_actors_fields_names[] = {
   "name", "class", "parent", "createdon", "validfrom", "expireson", "lastaccess", "actormark", "externalalias", NULL };
const char *NewActorsDB::table_actors_fields_types[] = {
   "char(64) primary key not null", "char(64)", "char(64)", "char(32)", "char(32)", "char(32)", "char(32)", "char(1)", "char(64)", NULL };
const char *NewActorsDB::table_users_fields_names[] = {
   "name", "credit", "quota", NULL };
const char *NewActorsDB::table_users_fields_types[] = {
   "char(64) primary key not null", "numeric(8,2)", "char(64)", NULL };
const char *NewActorsDB::table_computes_fields_names[] = {
   "name", "host", "capabilities", NULL };
const char *NewActorsDB::table_computes_fields_types[] = {
   "char(64) primary key not null", "char(256)", "char(4096)", NULL };
const char *NewActorsDB::table_workinstances_fields_names[] = {
   "name", "instanceid", "assignedcluster", "inputdatastore", "outputcompute", "resultblocks", NULL };
const char *NewActorsDB::table_workinstances_fields_types[] = {
   "char(64) primary key not null", "decimal(10,0)", "char(64)", "char(64)", "char(64)", "decimal(10,0)", NULL };
const char *NewActorsDB::table_datastores_fields_names[] = {
   "name", "blocksheld", "blocksize", "datahash", "instances", "origin", NULL };
const char *NewActorsDB::table_datastores_fields_types[] = {
   "char(64) primary key not null", "decimal(10,0)", "decimal(10,0)", "char(64)", "decimal(10,0)", "char(64)", NULL };
const char *NewActorsDB::table_keydependencies_fields_names[] = {
   "name", "keyname", NULL };
const char *NewActorsDB::table_keydependencies_fields_types[] = {
   "char(64)", "char(64)", NULL };

NewActorsDB::NewActorsDB(const char *filename) : SQLiteDB(filename, 1)
{
   createTablesIfNecessary();
}

char *NewActorsDB::makeFieldDefinitions(const char **dnames, const char **dtypes)
{
   char *table_fields = NULL;
   int all_len, i, j;
   all_len = 0; i = 0; j = 0;
   all_len += (strlen("("));
   while ((dnames[i] != NULL) && (dtypes[i] != NULL))
   {
      all_len += (strlen(dnames[i]) + strlen(" "));
      all_len += (strlen(dtypes[i]) + strlen(", "));
      i++;
   }
   all_len += (strlen(")"));
   all_len += 1;
   table_fields = (char *)malloc(sizeof(char)*all_len);
   table_fields[0] = '\0';
   strcat(table_fields, "(");
   while ((dnames[j] != NULL) && (dtypes[j] != NULL))
   {
      strcat(table_fields, dnames[j]);
      strcat(table_fields, " ");
      strcat(table_fields, dtypes[j]);
      if (j < (i-1)) strcat(table_fields, ", ");
      j++;
   }
   strcat(table_fields, ")");
   return table_fields;
}

char *NewActorsDB::makeCommaSeparatedList(const char **names)
{
   char *list = NULL;
   int all_len, i, j;
   all_len = 0; i = 0; j = 0;
   while (names[i] != NULL)
   {
      all_len += (strlen(names[i]) + strlen(","));
      i++;
   }
   all_len += 1;
   list = (char *)malloc(sizeof(char)*all_len);
   list[0] = '\0';
   while (names[j] != NULL)
   {
      strcat(list, names[j]);
      if (j < (i-1)) strcat(list, ",");
      j++;
   }
   return list;
}

void NewActorsDB::createTablesIfNecessary()
{
   char *actor_table_fields = makeFieldDefinitions(table_actors_fields_names, table_actors_fields_types);
   char *user_table_fields = makeFieldDefinitions(table_users_fields_names, table_users_fields_types);
   char *compute_table_fields = makeFieldDefinitions(table_computes_fields_names, table_computes_fields_types);
   char *workinstance_table_fields = makeFieldDefinitions(table_workinstances_fields_names, table_workinstances_fields_types);
   char *datastore_table_fields = makeFieldDefinitions(table_datastores_fields_names, table_datastores_fields_types);
   char *keydependency_table_fields = makeFieldDefinitions(table_keydependencies_fields_names, table_keydependencies_fields_types);
   ensureTablesAreCreated(6,
      "actors", actor_table_fields,
      "users", user_table_fields,
      "computes", compute_table_fields,
      "workinstances", workinstance_table_fields,
      "datastores", datastore_table_fields,
      "keydependencies", keydependency_table_fields);
   free(actor_table_fields);
   free(user_table_fields);
   free(compute_table_fields);
   free(workinstance_table_fields);
   free(datastore_table_fields);
   free(keydependency_table_fields);
}

int NewActorsDB::addActor_unsafe(const char *iname,
                                 const char *iclass,
                                 const char *iparent,
                                 const char *ivalidfrom,
                                 const char *iexpireson,
                                 const char *iactormark,
                                 const char *iexternalalias)
{
   FATAL_IF_BLANK(1, iname, "actor name");
   FATAL_IF_BLANK(1, iclass, "actor type");
   const char *tparent = (iparent) ? iparent : "NULL";
   const char *tvalidfrom = (ivalidfrom) ? ivalidfrom : "";
   const char *texpireson = (iexpireson) ? iexpireson : "";
   const char *tactormark = (iactormark) ? iactormark : "";
   const char *texternalalias = (iexternalalias) ? iexternalalias : "";
   char *timebuf_now = StringUtils::getDateTimeNowAsString();
   if (!timebuf_now) { errprint("could not get current time"); return 1; }
   sqlInstantExecf_unsafe("insert into 'actors' "
      "(name,class,parent,createdon,validfrom,expireson,lastaccess,actormark,externalalias) values "
      "('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s');",
         iname, iclass, tparent, timebuf_now, tvalidfrom, texpireson, timebuf_now, tactormark, texternalalias);
   free(timebuf_now);
   return 0;
}

int NewActorsDB::addUser(const char *ihandlename,
                         const char *iname,
                         const char *iparent,
                         int64_t credit,
                         int64_t quota,
                         int64_t lifetime)
{
   FATAL_IF_BLANK(1, ihandlename, "user handle name");
   char *timebuf_now = StringUtils::getDateTimeNowAsString();
   if (!timebuf_now) { errprint("could not get current time"); return 1; }
   char *timebuf_plustime = StringUtils::getDateTimeNowPlusThisMuchAsString(lifetime);
   if (!timebuf_plustime) { errprint("could not get current time plus time"); free(timebuf_now); return 1; }
   sqlite3_mutex_enter(dbmutex);
   int retval = addActor_unsafe(iname, "user", iparent, timebuf_now, (lifetime > 0) ? timebuf_plustime : NULL, NULL, ihandlename);
   if (retval == 0)
   {
      char *creditamount = (char *)malloc(sizeof(char)*(8+1+1+1+1));
      sprintf(creditamount, "%06d.%02d", (int)(credit / 100), (int)(credit % 100));
      sqlInstantExecf_unsafe("insert into 'users' "
                             "(name, credit, quota) values "
                             "('%s', '%qd', '%qd');",
                                iname, credit, quota);
   }
   sqlite3_mutex_leave(dbmutex);
   free(timebuf_now);
   free(timebuf_plustime);
   return retval;
}

int NewActorsDB::makeUserPermanent(const char *iname)
{
   FATAL_IF_BLANK(1, iname, "actor name");
   sqlInstantExecf("update 'users' set expireson='' where name='%s';", iname);
   return 0;
}

int NewActorsDB::addCompute(const char *iname,
                            const char *iparent,
                            int64_t durationseconds,
                            apr_sockaddr_t *cliaddr,
                            const char *capabilities,
                            const char *joblocation)
{
   FATAL_IF_BLANK(1, iname, "compute name");
   FATAL_IF_NULL(1, cliaddr, "client address data");
   FATAL_IF_BLANK(1, capabilities, "capabilities of compute");
   FATAL_IF_EXPRESSION(1, durationseconds <= 0, "invalid compute duration (%qd)", durationseconds);
   FATAL_IF_BLANK(1, joblocation, "job location (compute cluster + job ID)");
   char *timebuf_now = StringUtils::getDateTimeNowAsString();
   if (!timebuf_now) { errprint("could not get current time"); return 1; }
   char *timebuf_plustime = StringUtils::getDateTimeNowPlusThisMuchAsString(durationseconds);
   if (!timebuf_plustime) { errprint("could not get current time plus time"); free(timebuf_now); return 1; }
   sqlite3_mutex_enter(dbmutex);
   int retval = addActor_unsafe(iname, "compute", iparent, timebuf_now, timebuf_plustime, NULL, joblocation);
   if (retval == 0)
   {
      char *hostname;
      apr_status_t status;
      status = apr_getnameinfo(&hostname, cliaddr, 0);
      CheckAPRError(status);
      sqlInstantExecf_unsafe("insert into 'computes' "
                             "(name, host, capabilities) values "
                             "('%s', '%s', '%s');",
                                iname, hostname, capabilities);
   }
   sqlite3_mutex_leave(dbmutex);
   free(timebuf_now);
   free(timebuf_plustime);
   return retval;
}

int NewActorsDB::spawnWorkInstances(const char *iinputworkstore,
                                    int64_t durationseconds,
                                    int iinstances)
{
   FATAL_IF_BLANK(1, iinputworkstore, "input data store name");
   FATAL_IF_EXPRESSION(1, durationseconds <= 0, "invalid work instance duration (%qd)", durationseconds);
   FATAL_IF_EXPRESSION(1, iinstances <= 0, "invalid number of work instances (%d)", iinstances);
   char *timebuf_now = StringUtils::getDateTimeNowAsString();
   if (!timebuf_now) { errprint("could not get current time"); return 1; }
   char *timebuf_plustime = StringUtils::getDateTimeNowPlusThisMuchAsString(durationseconds);
   if (!timebuf_plustime) { errprint("could not get current time plus time"); free(timebuf_now); return 1; }
   const char *terrmsg = "";
   sqlite3_mutex_enter(dbmutex);
   char *parent = NULL;
   int retval = sqlGetSingleStringf_unsafe(&parent, "select parent from 'actors' where name='%s';", iinputworkstore);
   if (retval) terrmsg = "problem getting parent of work store";
   if (retval == 0)
   {
      for (int i = 0; i < iinstances; i++)
      {
         char *uuid_name = StringUtils::varprintf("%s_%d", iinputworkstore, i);
         retval = addActor_unsafe(uuid_name, "workinstance", parent, timebuf_now, timebuf_plustime, "N", NULL);
         if (retval) terrmsg = "problems adding actor";
         if (retval == 0)
         {
            sqlInstantExecf_unsafe("insert into 'workinstances' "
                                 "(name, instanceid, assignedcluster, inputdatastore, outputcompute, resultblocks) values "
                                 "('%s', '%d', '', '%s', '', '0');",
                                    uuid_name, i, iinputworkstore);
         }
      }
   }
   if (retval) errprint("%s", terrmsg);
   free(timebuf_now);
   free(timebuf_plustime);
   sqlite3_mutex_leave(dbmutex);
}

int NewActorsDB::addDataStore(const char *iname,
                              const char *iparent,
                              const char *iorigin,
                              const char *iallhash,
                              int noofblocks, int64_t blocksize)
{
   FATAL_IF_BLANK(1, iname, "data store name");
   FATAL_IF_BLANK(1, iparent, "data store parent user");
   FATAL_IF_BLANK(1, iallhash, "data store data hash");
   FATAL_IF_EXPRESSION(1, noofblocks <= 0, "invalid number of blocks (%d)", noofblocks);
   FATAL_IF_EXPRESSION(1, blocksize <= 0, "invalid block size (%qd)", blocksize);
   char *timebuf_now = StringUtils::getDateTimeNowAsString();
   if (!timebuf_now) { errprint("could not get current time"); return 1; }
   sqlite3_mutex_enter(dbmutex);
   int retval = addActor_unsafe(iname, "", iparent, timebuf_now, NULL, (iorigin) ? "w" : "r", NULL);
   if (retval == 0)
   {
      sqlInstantExecf_unsafe("insert into 'datastores' "
                             "(name, blocksheld, blocksize, datahash, instances, origin) values "
                             "('%s', '%d', '%qd', '%s', '1', '%s');",
                                iname, noofblocks, blocksize, iallhash, (iorigin) ? (iorigin) : (iparent));
   }
   sqlite3_mutex_leave(dbmutex);
   free(timebuf_now);
   return retval;
}

int NewActorsDB::addDependencyOnKey(const char *iname, const char *ikeyname)
{
   FATAL_IF_BLANK(1, iname, "name of actor who depends on the key");
   FATAL_IF_BLANK(1, ikeyname, "name of the key that the given actor depends on");
   sqlInstantExecf("insert into 'keydependencies' (name, keyname) values ('%s', '%s');", iname, ikeyname);
   return 0;
}

APRTable *NewActorsDB::getActor(const char *iname)
{
   FATAL_IF_BLANK(NULL, iname, "actor name");
   char *all_fields = makeCommaSeparatedList(table_actors_fields_names);
   sqlite3_mutex_enter(dbmutex);
   std::vector<char *> *actor_info = sqlScanRowTextf_unsafe("select %s from 'actors' where name='%s';", all_fields, iname);
   free(all_fields);
   FATAL_IF_BLANK_VECTOR_UNLOCK_MUTEX(NULL, actor_info, "an actor record");
   int i = 0;
   APRTable *ttable = new APRTable(20, NULL);
   while ((table_actors_fields_names[i] != NULL) && (i < actor_info->size()))
   {
      ttable->set(table_actors_fields_names[i], actor_info->at(i));
      i++;
   }
   StringUtils::destroyStringList(actor_info);
   int identflag = 0;
   if (strcmp(ttable->get("class"), "user") == 0)
   {
      all_fields = makeCommaSeparatedList(table_users_fields_names);
      std::vector<char *> *user_info = sqlScanRowTextf_unsafe("select %s from 'users' where name='%s';", all_fields, iname);
      free(all_fields);
      FATAL_IF_BLANK_VECTOR_UNLOCK_MUTEX(ttable, user_info, "extended user information");
      i = 1;
      while ((table_users_fields_names[i] != NULL) && (i < user_info->size()))
      {
         ttable->set(table_users_fields_names[i], user_info->at(i));
         i++;
      }
      StringUtils::destroyStringList(user_info);
      identflag = 1;
   }
   if (strcmp(ttable->get("class"), "compute") == 0)
   {
      all_fields = makeCommaSeparatedList(table_computes_fields_names);
      std::vector<char *> *compute_info = sqlScanRowTextf_unsafe("select %s from 'computes' where name='%s';", all_fields, iname);
      free(all_fields);
      FATAL_IF_BLANK_VECTOR_UNLOCK_MUTEX(ttable, compute_info, "extended compute information");
      i = 1;
      while ((table_computes_fields_names[i] != NULL) && (i < compute_info->size()))
      {
         ttable->set(table_computes_fields_names[i], compute_info->at(i));
         i++;
      }
      StringUtils::destroyStringList(compute_info);
      identflag = 1;
   }
   if (strcmp(ttable->get("class"), "workinstance") == 0)
   {
      all_fields = makeCommaSeparatedList(table_workinstances_fields_names);
      std::vector<char *> *workinstance_info = sqlScanRowTextf_unsafe("select %s from 'workinstances' where name='%s';", all_fields, iname);
      free(all_fields);
      FATAL_IF_BLANK_VECTOR_UNLOCK_MUTEX(ttable, workinstance_info, "extended work instance information");
      i = 1;
      while ((table_workinstances_fields_names[i] != NULL) && (i < workinstance_info->size()))
      {
         ttable->set(table_workinstances_fields_names[i], workinstance_info->at(i));
         i++;
      }
      StringUtils::destroyStringList(workinstance_info);
      identflag = 1;
   }
   if (strcmp(ttable->get("class"), "datastore") == 0)
   {
      all_fields = makeCommaSeparatedList(table_datastores_fields_names);
      std::vector<char *> *datastore_info = sqlScanRowTextf_unsafe("select %s from 'datastores' where name='%s';", all_fields, iname);
      free(all_fields);
      FATAL_IF_BLANK_VECTOR_UNLOCK_MUTEX(ttable, datastore_info, "extended data store information");
      i = 1;
      while ((table_datastores_fields_names[i] != NULL) && (i < datastore_info->size()))
      {
         ttable->set(table_datastores_fields_names[i], datastore_info->at(i));
         i++;
      }
      StringUtils::destroyStringList(datastore_info);
      identflag = 1;
   }
   if (strcmp(ttable->get("class"), "delegate") == 0)
   {
      identflag = 1;
   }
   if (!identflag) errprint("actor did not have a recognised extended type - type was '%s'!", ttable->get("class"));
   sqlite3_mutex_leave(dbmutex);
   return ttable;
}

int NewActorsDB::removeActor(const char *iname)
{
   FATAL_IF_BLANK(1, iname, "actor name");
   sqlite3_mutex_enter(dbmutex);
   char *classname = NULL;
   int retval = sqlGetSingleStringf_unsafe(&classname, "select class from 'actors' where name='%s';", iname);
   if (retval == 0)
   {
      if (strcmp(classname, "user") == 0)
         sqlInstantExecf_unsafe("delete from 'users' where name='%s';", iname);
      if (strcmp(classname, "compute") == 0)
         sqlInstantExecf_unsafe("delete from 'computes' where name='%s';", iname);
      if (strcmp(classname, "workinstance") == 0)
         sqlInstantExecf_unsafe("delete from 'workinstances' where name='%s';", iname);
      if (strcmp(classname, "datastore") == 0)
         sqlInstantExecf_unsafe("delete from 'datastores' where name='%s';", iname);
      sqlInstantExecf_unsafe("delete from 'actors' where name='%s';", iname);
      sqlInstantExecf_unsafe("delete from 'keydependencies' where name='%s';", iname);
   }
   sqlite3_mutex_leave(dbmutex);
   return retval;
}
